7장. 연결의 무게를 재는 자

출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 제이펍 2026) | 원서: Balancing Coupling in Software Design (Manning) · 입문판·PDF 재구성

코드는 분위기만 — namespace·interface·Execute 같은 말은 몰라도 됩니다. 표의 '비유'와 '위험'만 봐도 충분해요.

같은 "연결"이라도, 두 부품이 얼마나 무거운 지식을 주고받는지는 천차만별이다.

이 장은 그 무게를 4칸으로 줄 세운다.

침입 → 기능 → 모델 → 계약. 위가 무겁고(위험), 아래가 가볍다(안전).

이걸 통틀어 통합 강도라 부른다.


0. 이 장의 새 단어 (3개)

0장에 없던 말은 딱 3개다.

나머지 어려운 말(결합·상위/하위·공유 지식·캡슐화 경계·인터페이스·변경 전파·필수/우연 결합)은 전부 0장에 있다.

막히면 0장으로 돌아가면 된다.


통합 강도 (Integration Strength)

한 문장 뜻 — 두 부품의 연결이 얼마나 많은 지식을 나르는지를 무거운 순서로 4칸으로 줄 세운 것.

일상비유 — 택배 박스 무게 등급. 같은 "배송"이라도 1kg부터 30kg까지 있다. 등급을 알면 "이건 너무 무겁다"를 미리 안다.

한 줄 예 —

var levels = ["침입", "기능", "모델", "계약"]; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
// 왼쪽=무거움(위험), 오른쪽=가벼움(안전)

계약 (Contract)

한 문장 뜻 — 바깥과 주고받으려고 통합 전용으로 따로 만든 약속 양식. 속사정(내부 모델)과 분리돼 있다.

일상비유 — 표준 주문 양식. 식당 주방이 어떻게 돌아가는지는 안 적고, 주문에 꼭 필요한 칸(메뉴·수량)만 있다.

한 줄 예 —

public class OrderContract // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public string Item { get; init; } // 외부에 보여 줄 읽기용 값입니다.
    qty: int; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}

트랜잭션 (Transaction)

한 문장 뜻 — 여러 작업을 묶어 전부 성공하거나 전부 취소되는 한 덩어리.

일상비유 — 계좌 이체. 내 돈 빠지기와 상대 돈 들어오기가 함께 돼야 한다. 한쪽만 되면 사고다.

한 줄 예 —

using (var tx = transaction.Begin()) // 묶음 시작
    withdraw(a, 100); // 둘 다 성공해야
    deposit(b, 100); // 커밋. 하나라도 실패하면 둘 다 취소(롤백)

(귀납 도입) 이런 적 있죠?

남의 서비스 DB를 살짝 읽었을 뿐인데, 그 서비스가 컬럼 하나 바꾸자 내 코드가 조용히 터진 적 있죠?

또 이런 적도 있다. 두 서비스가 서로 부르지도 않는데, 무료 배송 기준을 800원으로 바꾸자 둘 다 동시에 고쳐야 했다.

같은 "연결"인데 왜 어떤 건 가볍고 어떤 건 이렇게 무거울까?

블라드 코노노프는 이 무게를 4칸으로 줄 세웠다.

아래 코드를 보자.

// 한 서비스가 다른 서비스의 "숨긴 DB"를 직접 읽는다
var rows = order_db.Query("SELECT * FROM cases"); // 공식 입구(API) 무시

이건 가장 무거운 1칸이다.

반대로 아래는 가장 가벼운 4칸이다.

var details = order_api.CasesByAgent("a1"); // 통합 전용 양식만 받음

둘 다 "연결"이지만 나르는 지식의 무게가 하늘과 땅이다.

이 무게 줄 세우기가 바로 통합 강도다.


이 장에서 딱 4가지만

이 장에서 딱 4가지만 — 무거운 순서로 외운다.

  1. 침입 — 영장 없이 남의 집 수색 (가장 무거움)
  2. 기능 — 같은 레시피를 두 식당이 따로 만듦
  3. 모델 — 공동 메뉴판을 두 식당이 같이 봄
  4. 계약 — 표준 주문 양식만 주고받음 (가장 가벼움)

위로 갈수록 서로 많이 알고(공유 지식↑), 변경이 멀리 번진다. 아래로 갈수록 서로 적게 알고, 변경이 번진다. 기본값은 항상 아래쪽(계약 결합)이다. 통신 방식이 동기든 비동기든, 이 무게는 바뀌지 않는다.


개념 1. 침입 결합 — 영장 없이 집 안 수색 (가장 무거움)

망가지는 장면

경찰이 영장도 없이 남의 집에 들어와 서랍을 뒤집는다.

집주인이 서랍 위치만 바꿔도 수색 계획이 통째로 깨진다.

게다가 집주인은 누가 집에 들어왔는지조차 모른다.

일상비유

영장(공개 인터페이스) 없는 집 수색.

문으로 안 들어오고 벽(비공개 속사정·내부 DB)을 직접 뚫는다.

비유 코드 위험
영장 없이 수색 service_b._verify(x) 숨긴 걸 바꾸면 침입자가 깨짐
영장 들고 방문(정상) serviceB.Charge(x) 안전 — 약속된 입구만 씀

정의 — 하위 부품이 상위 부품의 공개 인터페이스를 무시하고 통합용으로 만들어지지 않은 구현 세부를 직접 건드리는 것.

'침입적'이라 부르는 이유는, 이 통합을 상위 작성자가 고려한 적이 없음을 강조하기 위해서다.

옛 구조적 설계 시대엔 이걸 콘텐츠 결합이라 불렀다(둘은 같은 것).

예시 1 (worked — 완성예): 리플렉션으로 비공개 메서드 강제 호출

// InvoiceGenerator 가 일부러 숨긴(_verify_input) 메서드를
// 바깥에서 억지로 끄집어내 호출한다
var invoice = new InvoiceGenerator(); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
invoice.VerifyInput("input"); // _ = "쓰지 마세요" 표시인데 무시함

_verify_input은 소비자가 부르라고 만든 게 아니다.

이름을 바꾸거나 지우면 이 코드가 조용히 깨진다.

예시 2 (worked): 현대판 — 남의 서비스 DB 직접 접속

// 서비스 A 의 공식 API 를 안 쓰고, A 의 DB 에 직접 꽂는다
var rows = service_a_db.Query("SELECT * FROM orders"); // 속사정에 직접 침입

서비스 A가 컬럼 이름만 바꿔도 이 코드가 터진다.

서비스 A 작성자는 누가 자기 DB를 보고 있는지조차 모른다.

예시 3 (부분완성 — 빈칸 채우기)

아래 두 줄 중 침입 결합은 어느 쪽일까?

var a = report.BuildSql(filter); // (A)
var b = report.GetSummary(filter); // (B)

정답은 (A).

_로 숨긴 비공개 함수를 바깥에서 직접 부르니, 영장 없이 집에 들어간 것이다.

예시 4 (독립적용): 직접 판단

상용 기성 제품을 샀는데 알림 기능이 없다.

그래서 그 제품의 소스 코드를 직접 고쳐 알림을 끼워 넣었다.

이건 침입 결합일까?

그렇다. 이걸 역침입 결합이라 한다.

제품을 업그레이드하면 소스가 초기화되어 내 수정이 통째로 날아간다.

미니 시나리오 — 의도가 핵심

"ORM 프레임워크도 리플렉션으로 객체 필드를 만지는데, 그것도 침입 결합인가요?"

→ 아니다.

→ ORM을 쓰기로 한 순간, 그 접근을 의도적으로 허용한 것이다.

→ 침입을 만드는 건 리플렉션 기술이 아니라, 통합용이 아닌 것을 만지는 의도다.

왜 가장 무거운가 (3가지)

  1. 취약 — 상위의 어떤 변경도 통합을 깨뜨릴 수 있다.
  2. 암묵적 — 상위 작성자가 이 통합을 인식조차 못 한다 → 릴리스마다 검토 불가능.
  3. 캡슐화 파괴 — 상위의 모든 지식이 하위와 공유된다고 가정해야 한다.

단순 규칙 — 남이 숨긴 것(비공개 메서드·내부 DB·COTS 소스)을 직접 건드리면 침입 결합. 무조건 피한다.


개념 2. 기능 결합 — 같은 레시피를 두 식당이 따로 만듦

망가지는 장면

강남점과 강북점이 같은 본사 레시피대로 요리한다.

레시피가 바뀌면(재료 교체·조리 순서 변경) 두 지점 모두 동시에 고쳐야 한다.

한 지점만 고치고 다른 지점을 빠뜨리면, 같은 메뉴인데 맛이 달라진다(버그).

일상비유

같은 레시피로 따로 운영하는 두 식당.

직접 연결된 문은 없지만, 같은 레시피라는 보이지 않는 끈으로 묶여 있다.

비유 코드 위험
같은 레시피 두 식당 같은 배송 규칙 두 곳에 복붙 규칙 바뀌면 둘 다 고침
레시피 한 곳만 둠 규칙을 한 함수로 모음 안전 — 한 곳만 고침

정의 — 두 부품이 동일하거나 밀접하게 관련된 비즈니스 로직(기능)을 구현하는 것.

비즈니스 요구사항이 바뀌면 결합된 모든 부품에 동시에 적용해야 한다.

특이점 — 상위·하위가 없다

다른 수준과 달리 기능 결합은 상위-하위 관계가 없다.

지식이 양방향으로 흐른다.

하나가 바뀌면 다른 쪽도 바뀌어야 한다 → 둘 다 상위로 볼 수 있다.

식별 휴리스틱 — "이 비즈니스 요구사항이 바뀌면 여러 부품이 동시에 영향받을까?" 그렇다면 기능 결합일 가능성이 높다.

기능 결합엔 세 가지 정도가 있다(가벼움 → 무거움).


정도 1 — 순차적 기능 (특정 순서)

여러 작업을 정해진 순서로 호출해야 한다.

일상비유 — 라면은 물을 먼저 끓이고 그다음 면을 넣어야 한다. 순서가 뒤집히면 안 된다.

// 순서가 강제됨: 로그인 후에만 주문 가능
login(user); // 먼저
place_order(cart); // 그다음 — 순서 뒤집으면 실패

시간적 결합이라고도 한다.


정도 2 — 트랜잭션 기능 (한 덩어리)

여러 작업을 하나의 트랜잭션으로 처리해야 한다. 하나가 실패하면 전체 취소(롤백).

일상비유 — 계좌 이체는 출금과 입금이 함께 성공해야 한다. 한쪽만 되면 시스템이 망가진다.

// 둘이 함께 성공 또는 함께 실패해야 함
using (var tx = transaction.Begin()) // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    db.Update(order); // DB 갱신
    bus.Publish(event); // 메시지 발행 — 둘이 한 묶음

흔한 증상: 동시성 제어가 꼭 필요해진다(여러 사용자가 같은 데이터를 동시에 만지므로).


정도 3 — 대칭적 기능 (같은 기능을 복제)

두 부품이 동일한 기능을 각자 구현한다. DRY 원칙 위반이다.

성립 조건 둘: 1. 두 부품이 동일한 기능을 구현한다(알고리즘이 달라도 됨). 2. 요구사항이 바뀌면 모든 부품에서 동시에 고쳐야 한다.

// 잘못된 예 (before): 같은 규칙이 두 서비스에 복제됨
// 소매 서비스
static dynamic FreeShipRetail(dynamic o) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return o.Amount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}

// 배송 서비스
static dynamic FreeShipShip(dynamic o) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return o.Amount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}
// 기준이 800 으로 바뀌면? 둘 다 고쳐야 하고, 하나 빠뜨리면 버그
// 올바른 예 (after): 규칙을 한 곳에만 둠
static dynamic IsFreeShipping(dynamic o) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
    return o.Amount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}

var free_ship_retail = is_free_shipping; // 둘 다 같은 것을 부름
var free_ship_ship = is_free_shipping; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.

"모든 지식은 시스템 내에서 단일하고 모호하지 않으며 권위 있는 표현을 가져야 한다." — 앤드루 헌트·데이비드 토머스

왜 대칭적 기능이 두 번째로 무거운가? 1. 모든 변경이 두 부품에 동시에 반영돼야 한다. 2. 물리적 연결이 없어 매우 암묵적이다 — 자동으로 찾기 어렵다.

이게 바로 도입부의 "서로 부르지도 않는데 둘 다 고쳐야 했던" 그 상황이다.

예시 (독립적용)

주문 서비스와 정산 서비스가 각자 "세금 = 금액 × 0.1" 코드를 따로 들고 있다.

세율이 0.12로 바뀌면?

두 서비스를 동시에 고쳐야 한다 → 대칭적 기능 결합.

기능 결합의 영향

기능의 지식을 공유하면 인터페이스가 광범위·복잡해진다.

관계가 암묵적이 되어, 영향받는 부품에서 변경을 빠뜨리기 쉽다.

단순 규칙 — "요구사항이 바뀌면 함께 고쳐야 하는" 부품 쌍이 보이면 기능 결합. 로직은 한 곳에만 둔다.


개념 3. 모델 결합 — 공동 메뉴판을 두 식당이 같이 봄

망가지는 장면

강남점과 강북점이 공동 메뉴판(내부 도메인 모델)을 함께 본다.

메뉴판에서 항목이 추가·삭제되면, 메뉴판을 참조하는 모든 직원이 영향받는다.

주방 운영(내부 로직)은 각자 따로 하지만, 메뉴판이라는 데이터 구조는 묶여 있다.

일상비유

두 식당이 공유하는 공동 메뉴판.

행동(레시피)이 아니라 데이터 양식을 공유한다.

비유 코드 위험
공동 메뉴판 c = dist.get_customer(id) 내부 모델 바꾸면 소비자 영향
각자 만든 양식 summary = dist.get_summary(id) 안전 — 통합용 양식만 봄

정의 — 상위 부품이 내부 비즈니스 도메인 모델을 공개 인터페이스의 일부로 그대로 노출하고, 하위 부품이 그 모델에 의존하는 것.

행동이 아니라 데이터 모델만 공유한다(행동 공유는 기능 결합 쪽이다).

"모든 모델은 틀렸지만 일부 모델은 유용하다." — 조지 박스

모델은 실제 세계를 완전히 베끼는 게 아니라, 특정 문제를 풀기에 충분한 정보만 담는다.

예시 1 (worked): 내부 모델을 그대로 노출 (before)

// Distribution 이 내부 Customer 를 통째로 바깥에 돌려준다
public class Customer // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id; name; email; phone; /* 구현 생략 */ // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

}
static dynamic GetCustomer(dynamic id) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return fetch(id); // 내부 모델을 그대로 반환

}
// Accounting 이 이 Customer 에 의존
var c = dist.GetCustomer(id); // Distribution 이 phone 을 지우면 Accounting 도 깨짐

예시 2 (worked): 한 개체, 두 목적 → 두 모델

같은 "지원 사례"라도 목적이 다르면 모델도 달라야 한다.

// 운영용 모델: 사례를 처리하는 데 필요한 칸
public class SupportCaseOps // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id; title; assignee; status; messages; // 운영 중심

}
// 분석용 모델: 통계 내는 데 필요한 칸
public class SupportCaseAnalytics // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id; reopened_count; reassigned_count; // 분석 중심
    avg_response_time; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}

둘을 하나로 합치면, "모든 분야에 능통하지만 어느 분야에도 정통하지 못한" 비대한 모델이 된다.

예시 3 (부분완성): 모델 결합으로 만드는 빈칸

서비스 B가 서비스 A의 운영 DB를 읽기 전용으로 본다.

A의 내부 모델 그대로를 읽는다면 이건 무슨 결합?

var data = service_a_db.Read("customers"); // A 의 내부 모델 그대로 읽음
// 정답: _____ 결합

정답: 모델 결합.

(침입과 달리 A가 읽기를 허용했고, 공유되는 건 행동이 아니라 데이터 모델이라서다.)

예시 4 (독립적용)

서비스 A가 메시지 버스에 내부 도메인 모델 형식 그대로 데이터를 올리고, 서비스 C가 그 구조를 알아야 한다.

무슨 결합?

모델 결합이다. 통신이 비동기라도 공유되는 건 내부 데이터 모델이니까.

모델 결합이 기능 결합보다 가벼운 이유 (3가지)

  1. 데이터 구조만 공유(행동은 안 함) → 데이터는 행동보다 안정적.
  2. 인터페이스가 더 명확 → 문서화·중단 변경 식별 가능.
  3. 의도적 접근 → 상위가 공유 사실을 알고 관리할 수 있다.

모델 결합의 위험

하위가 공유 모델의 일부만 쓰면, 상위는 불필요한 정보까지 함께 공유하게 된다(구조적 설계의 스탬프 결합과 닮음).

모델이 시스템 전체로 퍼지면, 많은 부품이 최적화 안 된 모델에 묶인다.

단순 규칙 — 내부 도메인 모델을 공개 인터페이스에 그대로 노출하면 모델 결합. 통합 전용 양식으로 바꾸면 더 가벼워진다.


개념 4. 계약 결합 — 표준 주문 양식만 주고받음 (가장 가벼움)

망가지는 장면

(여긴 거의 안 망가진다. 가장 가벼운 등급이라서다.)

강남점·강북점이 표준 주문 양식(계약)만 공유한다.

양식엔 "메뉴명·수량·테이블번호"만 있다.

각 지점의 내부 운영(재료 구매·조리·직원 배치)은 완전히 분리된다.

강남점이 조리 방식을 바꿔도, 양식 형식이 그대로면 강북점은 영향 없다.

일상비유

표준 주문 양식만 공유.

내부 속사정 대신, 통합 전용으로 따로 만든 약속만 오간다.

비유 코드 위험
표준 주문 양식 api.cases_by_agent("a1") 거의 안 깨짐
내부 객체 통째 api.get_internal_case() 내부 바뀌면 소비자 깨짐

정의 — 상위 부품이 통합 전용으로 따로 설계한 계약을 통해 하위와 통신하는 것.

내부 구현 모델과 계약은 분리돼 있다.

계약은 "협업 조건을 적은 협약"으로, 어떤 의미에서 모델의 모델이다.

예시 1 (worked): 내부 모델과 계약을 분리 (after)

// 내부 운영 모델 (안에서만 씀) — 도메인 전용 타입들
public class SupportCase // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id: CaseId; // 도메인 전용 타입
    assignee: AgentId; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    status: CaseStatus; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

}
// 통합 전담 계약 (바깥 노출용) — 단순 타입으로 변환
public class SupportCaseDetails // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public string Id { get; init; } // 외부에 보여 줄 읽기용 값입니다.
    public string AssignedAgentId { get; init; } // 외부에 보여 줄 읽기용 값입니다.
    public string Status { get; init; } // 외부에 보여 줄 읽기용 값입니다.
}

내부 SupportCase가 어떻게 바뀌든, SupportCaseDetails만 안정적이면 하위는 영향 없다.

예시 2 (worked): 작업 기반 계약 — 명령과 쿼리

// 계약을 "할 수 있는 작업"의 언어로 짠다
public class EscalateCase // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public string CaseId { get; init; } // 외부에 보여 줄 읽기용 값입니다.
    public string Reason { get; init; } // 외부에 보여 줄 읽기용 값입니다.

}
// 명령 실행 + 쿼리
api.Execute(EscalateCase("c1", "긴급")); // 명령: 무언가 시킴
var cases = api.CasesByAgent("a1", "open"); // 쿼리: 정보 가져옴

계약은 "무엇을 할 수 있는가"만 노출하고, "어떻게 하는가"는 완전히 감춘다.

예시 3 (부분완성): 진짜 계약 vs 가짜 계약 (함정)

계약처럼 보여도, 내부 모델을 그대로 베끼면 사실은 모델 결합이다.

public class Message // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id; body; sent_on; sent_by; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

}
public class MessageDTO // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    id; body; sent_on; sent_by; // Message 와 완전히 똑같음
}
// 이건 진짜 계약일까?  정답: _____

정답: 아니다(사실상 모델 결합).

MessageDTO는 아무 지식도 숨기지 않는다.

Message가 바뀌면 MessageDTO도 따라 바뀔 가능성이 높다.

계약이 가치 있으려면 내부 세부를 실제로 추상화해야 한다.

예시 4 (독립적용): 모듈 깊이로 판단

4장의 모듈 깊이를 떠올려 보자(사각형 면적=숨긴 것, 밑변=공개 인터페이스).

MessageDTO는 숨긴 게 없으니 면적이 밑변과 같다 → 얕은 모듈.

얕은 계약은 가치가 없고 불필요한 부품만 늘려 시스템을 더 복잡하게 만든다.

계약 결합의 이점 (3가지)

  1. 계약이 구현 모델보다 안정적 → 내부는 자유롭게 진화해도 계약만 유지되면 전파 없음.
  2. 공유 지식 최소화 → 계약만 노출, 구현 모델은 경계 뒤에 숨김.
  3. 버전 관리 가능 → 여러 계약 버전을 동시에 노출해 소비자가 천천히 이사하게 한다.

계약은 직접 만들 필요도 없다

GoF 디자인 패턴이 계약 결합을 여러 형태로 구현한다. - 파사드 — 하위 시스템 묶음에 통합된 단일 인터페이스 제공. - 브리지 — 추상화와 구현을 분리해 따로 진화. - 어댑터 — 호환 안 되는 인터페이스를 이어 줌.

표준 프로토콜도 계약이다 — 이메일의 SMTP·IMAP·POP3가 그 예다.

미니 시나리오 — 도구지 목표가 아니다

"그럼 항상 계약 결합으로 만들면 되나요?"

→ 아니다.

→ 위 MessageDTO처럼 아무것도 숨기지 않는 얕은 계약은 오히려 해롭다.

→ 계약 결합은 도구 상자의 한 도구일 뿐, 절대적 최종 목표가 아니다.

단순 규칙 — 통합 전용 양식이 내부 세부를 실제로 추상화하면 계약 결합. 그게 새 코드의 기본값이다.


통신이 동기든 비동기든, 무게는 그대로

흔한 오해: "메시지 큐(비동기)를 쓰면 무조건 느슨하게 결합된다."

정답: 통신 방식은 통합 강도를 바꾸지 않는다.

중요한 건 경계를 넘어 공유되는 지식의 무게다.

같은 메시지 큐를 써도, 안에 무엇을 담느냐로 등급이 갈린다.

큐에 담는 것 통합 강도
통합 전용 계약 양식 계약 결합
내부 도메인 모델 그대로 모델 결합
일정 지연 후에만 처리(타이밍 의존) 기능 결합(순차적)
생산자·소비자가 함께 성공/실패해야 함 기능 결합(트랜잭션)
메시지 버스 자체가 내부 구현 세부 침입 결합

핵심: 어떻게 보내는가가 아니라 무엇을 공유하는가가 무게를 정한다.


같은 공유 DB, 네 가지 무게

똑같이 "DB를 공유"해도, 어떻게 접근하느냐에 따라 무게가 다르다.

접근 방식 통합 강도
A의 내부 DB를 B가 몰래 직접 읽음 침입 결합
두 서비스가 같은 테이블에 같은 비즈니스 규칙으로 읽고 씀 기능 결합
A가 DB를 공개로 허용, B가 A의 내부 모델을 읽음 모델 결합
통합 전용 테이블(제한된 스키마)을 두고 B가 읽음 계약 결합

같은 설계 그림이라도 의도와 공유 지식이 무게를 가른다.


분산 시스템 한 장면 — 무게가 섞여 있다

서비스 A가 DB와 메시지 버스로 여러 서비스와 엮인 장면:

  • A의 DB 갱신과 이벤트 발행이 함께 성공/실패해야 함 → 기능 결합(트랜잭션, 값 공생성).
  • 서비스 C가 A의 내부 도메인 모델을 받아 씀 → 모델 결합.
  • 서비스 D가 C의 처리 시간을 벌려고 30초 지연 후 실행 → 기능 결합(순차적, 타이밍 공생성).

한 시스템 안에서 여러 무게가 섞여 있는 게 정상이다. 그래서 부품 쌍마다 따로 진단한다.


4수준 한 줄 묶음표

수준 일상 비유 핵심 신호 공유 지식 방향
침입 (무거움) 영장 없는 집 수색 비공개 침범·남의 DB 직접 구현 세부 전부 단방향
기능 같은 레시피 두 식당 같은 규칙 복제·순서·트랜잭션 비즈니스 로직 양방향
모델 공동 메뉴판 내부 도메인 모델 노출 데이터 모델 단방향
계약 (가벼움) 표준 주문 양식 통합 전용 양식·DTO 통합 전담 모델만 단방향

위로 갈수록: 공유 지식↑ · 변경 전파↑ · 암묵적↑

아래로 갈수록: 공유 지식↓ · 인터페이스 명확↑ · 변경 안전↑


옛 모델과의 짝 맞추기 (참고)

이 4수준은 더 오래된 두 모델의 핵심을 하나로 합친 것이다.

통합 강도 닮은 옛 결합(구조적 설계)
침입 결합 콘텐츠 결합 (같은 말)
기능 결합 공통·외부·제어 결합
모델 결합 스탬프 결합
계약 결합 데이터 결합

단, 대칭적 기능 결합(서로 안 부르는데 같은 로직 복제)은 옛 두 모델 어느 쪽도 못 잡던 사각지대다.

통합 강도가 이 빈틈을 메운다.


정리

  • 같은 "연결"도 무게가 있다. 침입 → 기능 → 모델 → 계약 (무거움 → 가벼움).
  • 위로 갈수록 많이 알고 변경이 멀리 번진다. 아래로 갈수록 가볍고 안전하다.
  • 통신이 동기든 비동기든 무게는 그대로다 — 무엇을 공유하는가가 전부다.
  • 기본값은 가장 가벼운 계약 결합. 더 무거운 게 필요하면 이유를 댄다.

한 걸음 더 ▸ (지금 몰라도 됨)

"계약 결합 = 무조건 정답"은 아니다. 아무것도 숨기지 않는 얕은 계약(MessageDTO 같은)은 오히려 부품만 늘려 해롭다. 같은 무게라도 언제 받아들일 만한지는 더 깊은 주제라, 지금은 "적게 공유할수록 가볍다" 규칙 하나만 들고 가면 된다.


더 해보기

분산 시스템에서 침입·모델 결합을 피하는 정석은 서비스마다 DB를 따로 두는 것이다. Database per Service 패턴 · 이벤트를 명시적 계약으로 다루는 AsyncAPI 표준 (검증 2026-05-31).


다음 8장 예고 — 연결의 무게 말고도 결합을 좌우하는 또 다른 잣대(거리)를 본다. (지금 몰라도 됩니다 — 8장에서 천천히 풀려요.)


연습문제

  1. 설명. 연결의 무게를 재는 자의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라.
  2. 구분. 두 개념(계약 결합, 외부 결합)을 실제 예시 하나로 구분하라.
  3. 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.

부록 A. 쉬운 용어 사전

한글 용어 원문 영문명 아주 쉬운 뜻 이 장에서 나온 위치
계약 결합 Contract Coupling 공개된 약속이나 계약을 통해 필요한 정보만 주고받는 결합. 부록 B와 본문 예시
외부 결합 External Coupling 공통 파일, DB, 프로토콜 같은 외부 자원에 함께 묶이는 결합. 부록 B와 본문 예시
공유 DB Shared Database 여러 부품이 같은 데이터베이스 구조를 직접 알고 쓰는 방식. 부록 B와 본문 예시
공개 계약 Public Contract 내부 구조 대신 공개된 인터페이스 약속으로 주고받는 방식. 부록 B와 본문 예시

부록 B. 헷갈리는 개념 비교표

A B 구분 포인트
계약 결합 외부 결합 계약 결합은 공개 약속으로 주고받고, 외부 결합은 공동 외부 자원에 묶인다.
공유 DB 공개 계약 공유 DB는 내부 구조를 같이 알고, 공개 계약은 약속된 인터페이스만 안다.

부록 C. 더 읽을 자료

  • 이 장의 더 해보기 섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다.
  • 같은 책의 0장 한눈에 보기 — 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다.
  • 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
  • 이 장의 flashcards.json — 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.

부록 D. 연습문제 풀이

  1. 설명 예시. 연결의 무게를 재는 자는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다.
  2. 구분 예시. 두 개념(계약 결합, 외부 결합)의 차이는 이렇게 잡으면 된다. 계약 결합은 공개 약속으로 주고받고, 외부 결합은 공동 외부 자원에 묶인다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다.
  3. 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플